package com.googlecode.cswish.struts.spring;

import java.io.File;
import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.springframework.stereotype.Service;

import com.googlecode.cswish.Config;
import com.googlecode.cswish.model.CrossSiteModel;
import com.googlecode.cswish.struts.hibernate.DynamicClassLoaderFactory;
import com.googlecode.cswish.struts.hibernate.ehcache.EhCacheRegionFactory;
import com.googlecode.cswish.util.crosssite.CrossSiteArrayList;
import com.googlecode.cswish.util.crosssite.CrossSiteBoundedMap;
import com.googlecode.cswish.util.crosssite.CrossSiteCacheMap;
import com.googlecode.cswish.util.crosssite.CrossSiteLRUCacheMap;

@Service
public class CacheService {

	@Resource
	private DynamicClassLoaderFactory dynamicClassLoaderFactory;
	
	@Resource
	private Config config;
	
	private List<WeakReference<CrossSiteModel>> siteVarRef;
	
	private int varSeq;
	private Map<String, Object> crossSiteVars;
	
	@PostConstruct
	private void init() {
		siteVarRef = new LinkedList<WeakReference<CrossSiteModel>>();
		crossSiteVars = newCache(Object.class);
	}
	
	public <T> CrossSiteCache<T> newCache(Class<T> clazz) {
		return newCach(clazz, 0, null, false);
	}
	
	public <T> Cache<List<T>> newCaches(Class<T> clazz) {
		return newCach(clazz, 0, null, false);
	}
	
	public <T> Cache<List<T>> newCaches(Class<T> clazz, int maxElement) {
		return newCach(clazz, maxElement, null, false);
	}
	
	public <T> Cache<T> newCache(Class<T> clazz, int maxElement) {
		return newCach(clazz, maxElement, null, false);
	}
	
	public <T> Cache<T> newCache(Class<T> clazz, Map<String, T> initValues) {
		return newCach(clazz, 0, initValues, false);
	}
	
	public <T> Cache<T> newLinkedCache(Class<T> clazz, Map<String, T> initValues) {
		return newCach(clazz, 0, initValues, true);
	}
	
	public <T> Cache<T> newShareCache(Class<T> clazz) {
		Cache<T> cache = new ShareConcurrentCache<T>();
		if (!clazz.getName().startsWith("java.")) {
			dynamicClassLoaderFactory.registerUnloadCacheEvent(clazz.getName(), cache);
		}
		return cache;
	}
	
	private CrossSiteCache newCach(Class clazz, int maxElement, Map initValues, boolean useLinkedMap) {
		CrossSiteCache cache;
		if (!config.isEnableSaas()) {
			// TODO: create simple cache
		}
		if (maxElement == 0) {
			cache = createConcurrentMap(initValues, useLinkedMap);
		} else {
			cache = createLRUMap(maxElement);
		}
		
		if (clazz != null && !clazz.getName().startsWith("java.")) {
			dynamicClassLoaderFactory.registerUnloadCacheEvent(clazz.getName(), cache);
		}
		
		if (cache instanceof CrossSiteModel) {
			CrossSiteModel crossSiteModel = (CrossSiteModel)cache;
			siteVarRef.add(new WeakReference<CrossSiteModel>(crossSiteModel));
		}
		return cache;
	}
	
	private <V> CrossSiteCache<V> createConcurrentMap(final Map<String, V> initValues, boolean useLinkedMap) {
		if (useLinkedMap) {
			return new CrossSiteCacheMap<V>(initValues, new Executor<Map<String, V>, Object>() {
				@Override
				public Map<String, V> execute(Object parameters) {
					return new LinkedHashMap<String, V>();
				}
			});
		} else {
			return new CrossSiteCacheMap<V>(initValues);
		}
	}
	
	private <V> CrossSiteCache<V> createLRUMap(int maxEntries) {
		return new CrossSiteLRUCacheMap<V>(null, maxEntries);
	}
	
	private class ShareConcurrentCache<V> extends ConcurrentHashMap<String, V> implements Cache<V> {
	
		private static final long serialVersionUID = 7837410831144465975L;

		@Override
        public void removeAll(String key) {
			super.remove(key);
		}
	}
	
	public void removeSiteCache(String site) {
		Iterator<WeakReference<CrossSiteModel>> iter = siteVarRef.iterator();
		while (iter.hasNext()) {
			WeakReference<CrossSiteModel> ref = iter.next();
			CrossSiteModel crossSiteModel = ref.get();
			if (crossSiteModel != null) {
				crossSiteModel.destroy(site);
			} else {
				iter.remove();
			}
		}
		
		// remove second level cache (see SiteCache)
		EhCacheRegionFactory.getInstance().removeAllSiteCache(site);
	}
	
	public synchronized <T> Wrapper<T> newVar(Class<T> clazz) {
		return newVar(clazz, null);
	}
	
	public synchronized <T> Wrapper<T> newVar(Class<T> clazz, T defaultValue) {
		varSeq++;
		Wrapper<T> wrapper = new Wrapper<T>(String.valueOf(varSeq), defaultValue, null);
		return wrapper;
	}
	
	public synchronized <T> Wrapper<T> newVarWithCreator(Class<T> clazz, Executor<T, String> creator) {
		varSeq++;
		Wrapper<T> wrapper = new Wrapper<T>(String.valueOf(varSeq), null, creator);
		return wrapper;
	}
	
	public class Wrapper<T> {
		
		private String name;
		private T defaultValue;
		private Executor<T, String> creator;
		
		public Wrapper(String name, T defaultValue, Executor<T, String> creator) {
			this.name = name;
			this.defaultValue = defaultValue;
			this.creator = creator;
		}
		
		public T get() {
			T t = (T)crossSiteVars.get(name);
			if (t == null) {
				if (creator != null) {
					synchronized (crossSiteVars) {						
						t = creator.execute(name);
						// TODO: throw exception if t is null?
						crossSiteVars.put(name, t);
					}
				} else {
					t = defaultValue;
				}
			}
			return t;
		}
		
		public void set(T var) {
			crossSiteVars.put(name, var);
		}
	}
	
	public class FileVarWrapper<T> extends Wrapper<T> {
		private File file;
		private long lastCheckTime = 0;
		private long lastModified = 0;
		
		public FileVarWrapper(String name, T defaultValue,
				Executor<T, String> callback, File file) {
			super(name, defaultValue, callback);
			this.file = file;
		}
		
		public T get() {
			if (System.currentTimeMillis() - lastCheckTime > 5000) {
				if (file.lastModified() != lastModified) {
					// file changed, make the variable invalid
					return null;
				}
			}
			
			T ret = super.get();
			return ret;
		}
		
		public void set(T var) {
			super.set(var);
			
			lastModified = file.lastModified();
			lastCheckTime = System.currentTimeMillis();
		}
		
		public File getFile() {
			return file;
		}
	}
	
	public <T> List<T> newList(List<T> initValues) {
		CrossSiteArrayList<T> list = new CrossSiteArrayList<T>(initValues);
		siteVarRef.add(new WeakReference<CrossSiteModel>(list));
		return list;
	}

	// TODO: add this method here?
	public CrossSiteBoundedMap newBoundedMap(Map oldValue) {
		CrossSiteBoundedMap map = new CrossSiteBoundedMap(oldValue);
		siteVarRef.add(new WeakReference<CrossSiteModel>(map));
		return map;
	}
}